package com.jgzj.example.export.service.impl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jgzj.example.export.config.ExportListenerProcessor;
import com.jgzj.example.export.config.QueryMethodInfo;
import com.jgzj.example.export.format.ExportFormatReq;
import com.jgzj.example.export.model.export.BaseExport;
import com.jgzj.example.export.model.export.ExportReqDto;
import com.jgzj.example.export.service.ExportService;
import com.jgzj.example.export.util.SpringContextUtil;
import com.jgzj.example.export.vo.BaseReqVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

/**
 * @description:
 * @author: 子木
 * @since: 2021/05/16
 */
@Service
@Slf4j
public class ExportServiceImpl implements ExportService {

    private static final int DEFAULT_PAGE_SIZE = 1000;
    private static final int DEFAULT_PAGE_NO = 1;
    private static final int DEFAULT_TOTAL_PAGE = 10;

    @Autowired
    private ExportListenerProcessor exportListenerProcessor;

    @Override
    public byte[] download(ExportReqDto reqDto) {
        BaseReqVo baseReqVo = JSON.parseObject(JSON.toJSONString(reqDto.getQueryParams()), BaseReqVo.class);
        this.initReqParams(baseReqVo);
        int pageNo = baseReqVo.getPageNo();
        int totalPage = baseReqVo.getTotalPage();
        int pageSize = baseReqVo.getPageSize();

        byte[] result = null;
        final QueryMethodInfo methodInfo = this.getMethodInfo(reqDto.getType());
        final Sheet sheet = this.getSheet(methodInfo, reqDto.getSheetName());
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {

            Function function = (Function) methodInfo.getExportFormat().newInstance();
            ExcelWriter excelWriter = this.getExcelWriter(methodInfo, out);
            for (int i = pageNo; i <= pageNo + totalPage - 1; i++) {
                baseReqVo.setPageNo(i);
                List<T> pageData = this.getDownloadData(baseReqVo, reqDto);
                ArrayList<Object> realExportList = new ArrayList<>();
                if (pageData!=null) {
                    ExportFormatReq exportFormatReq = new ExportFormatReq();
                    exportFormatReq.setClazz(methodInfo.getModelClass());
                    for (Object item : pageData) {
                        exportFormatReq.setData(item);
                        realExportList.add(function.apply(exportFormatReq));
                    }
                }
                excelWriter.write(realExportList,sheet);
                if (realExportList.size()<pageSize) {
                    break;
                }
            }
            excelWriter.finish();
            result = out.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("导出有误",e.getCause());
        }

        return result;
    }


    private void initReqParams(BaseReqVo reqDto) {
        int total = DEFAULT_TOTAL_PAGE * DEFAULT_PAGE_SIZE;
        if (null == reqDto.getPageNo()) {
            reqDto.setPageNo(DEFAULT_PAGE_NO);
        }
        if (null == reqDto.getTotalPage()) {
            reqDto.setTotalPage(DEFAULT_TOTAL_PAGE);
        }
        if (null == reqDto.getPageSize()) {
            reqDto.setPageSize(DEFAULT_PAGE_SIZE);
        } else {
            total = reqDto.getTotalPage() * reqDto.getPageSize();
            reqDto.setPageSize(reqDto.getPageSize() * reqDto.getTotalPage() < DEFAULT_PAGE_SIZE * DEFAULT_TOTAL_PAGE ?
                    reqDto.getPageSize() * reqDto.getTotalPage() : DEFAULT_PAGE_SIZE * DEFAULT_TOTAL_PAGE);
        }
        reqDto.setTotalPage(total / reqDto.getPageSize() > 0 ? total / reqDto.getPageSize() : 1);
    }

    private QueryMethodInfo getMethodInfo(String type) {
        return exportListenerProcessor.getMap().get(type);
    }

    private Sheet getSheet(QueryMethodInfo methodInfo, String sheetName) {
        Sheet sheet = null;
        if (methodInfo.getColumnNames() != null && !methodInfo.getColumnNames().isEmpty()) {
            sheet = new Sheet(1, 0);
            sheet.setHead(methodInfo.getColumnNames());
        } else {
            sheet = new Sheet(1, 0, methodInfo.getModelClass());
        }
        sheet.setSheetName(sheetName);
        return sheet;
    }

    private ExcelWriter getExcelWriter(QueryMethodInfo methodInfo, ByteArrayOutputStream out) {
        ExcelWriter excelWriter = null;
        if (methodInfo.getExcludeColumnNames() != null && !methodInfo.getExcludeColumnNames().isEmpty()) {
            excelWriter = EasyExcel.write(out, methodInfo.getModelClass()).excludeColumnFiledNames(methodInfo.getExcludeColumnNames()).build();
        } else if (methodInfo.getIncludeColumnNames() != null && !methodInfo.getIncludeColumnNames().isEmpty()) {
            excelWriter = EasyExcel.write(out, methodInfo.getModelClass()).includeColumnFiledNames(methodInfo.getIncludeColumnNames()).build();
        } else {
            excelWriter = EasyExcel.write(out, methodInfo.getModelClass()).build();
        }
        return excelWriter;
    }

    private List<T> getDownloadData(BaseReqVo baseReqVo, ExportReqDto reqDto) {
        List<T> result = null;
        try {
            final QueryMethodInfo methodInfo = this.getMethodInfo(reqDto.getType());
            BaseExport baseExport = (BaseExport<T>)methodInfo.getParameterType().newInstance();
            result = (List<T>) methodInfo.getMethod().invoke(SpringContextUtil.getBean(methodInfo.getClazz())
                    , baseExport.getCurrentObject(paramsJoin(baseReqVo, reqDto.getQueryParams())));
        }catch (Exception e) {
            log.error("invoke fail, please check @Export, this method parameter is one, and return is IPage<T>");
        }
        return result;
    }

    private String paramsJoin(BaseReqVo baseQueryDto, Object extendQueryDto) {
        JSONObject dto1 = JSONObject.parseObject(JSON.toJSONString(baseQueryDto));
        JSONObject dto2 = JSONObject.parseObject(JSON.toJSONString(extendQueryDto));
        dto2.putAll(dto1);
        return dto2.toJSONString();
    }
}